package vsa.migrations.junit;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.emftext.language.java.JavaClasspath;
import org.emftext.language.java.classifiers.Class;
import org.emftext.language.java.containers.CompilationUnit;
import org.emftext.language.java.resource.JaMoPPUtil;
import org.emftext.language.java.resource.java.mopp.JavaResource;

import vsa.migrations.junit.config.JunitMigrationConfigurator;
import vsa.migrations.junit.envreader.EnvReader;
import vsa.migrations.junit.envreader.EnvReaderDotProject;
import vsa.migrations.junit.migrators.JunitMigrator;

/**
 * if compileunit is a testcase (one of the superclasses == TestCase
 * then:
 * - annotate with Runwith(Runner)
 * - add import for RunWith
 * - add import for Runner
 * - annotate methods, which are not static, returning void and start with test with @Test
 * - add import for @Test on demand
 * - annotate protected void method setUp() with @Before and make it public
 * - annotate protected void method tearDown() with @After and make it public
 * - remove all non-default-constructors
 * - If current constructor is default-constructor and only contains an super-call without parameters, remove it
 * - If no default-constructor is available, but non-default-constructor are available,
 *   we have to add a default-constructor
 * - If no nondefault-constrcutor is available and the default-constructor only calls super, then we can remove it too
 * - Remove all junit.framework imports remove junit.framework.Assert import and add static import instead
 *
 *
 * @author oleym
 *
 */
public class JunitMigrationApplication {


	private static Logger LOGGER = Logger.getLogger(JunitMigrationApplication.class.getName());


	private MigrationUtil migrationutil = new MigrationUtil();

	private JunitMigrationConfigurator configurator = new JunitMigrationConfigurator();
	
	private EnvReader envReader = new EnvReaderDotProject();
	
	


	public JunitMigrationApplication() {
		LOGGER.info("Started migration with maxmemory " + Runtime.getRuntime().maxMemory());
	}
	
	/**
	 * setter
	 * @param envReader set a different envreader 
	 */
	public void setEnvReader (final EnvReader envReader) {
		this.envReader = envReader;
	}

	/**
	 * find javafiles recursively in path
	 * @param foundFiles list of found files
	 * @param path path to find files in
	 * @throws IOException
	 */
	public void findJavafiles (final List<File> foundFiles, final File path, final File basepath, boolean saveHandling) throws IOException {
		if (path.isDirectory()) {
			for (File nextFile: path.listFiles())
				findJavafiles(foundFiles, nextFile, basepath, saveHandling);
		}

		if (path.exists() && path.getName().endsWith(".java")) {
			
			String pathAsString = path.getAbsolutePath().substring(basepath.getAbsolutePath().length() + 1); 
			LOGGER.fine("Found relative path " + pathAsString);
			
			for (String excludes :configurator.getExcludePaths()) {
				if (pathAsString.startsWith(excludes)) {
					LOGGER.fine("Ignoring file " + path.getAbsolutePath() + " because matching exclude path " + excludes);
					return;
				}
			}
			LOGGER.fine("Found javafile " + path.getAbsolutePath());
			foundFiles.add(path);
		}
	}


	/**
	 * loads the found javafiles as resources
	 * @param javafiles javafiles
	 * @param path path
	 * @return resourceset
	 */
	private ResourceSet createResourceSet (final List<File> javafiles, final File path) {
		ResourceSet rs = new ResourceSetImpl();
        JavaClasspath cp = JavaClasspath.get(rs);
        cp.registerStdLib();
        envReader.readEnvironment(cp, new File (""));
        
        int i = 0; 
        for (File nextJavaFile: javafiles) {
        	URI uri = URI.createFileURI(nextJavaFile.getAbsolutePath());
        	boolean nameContainsText = nextJavaFile.getName().contains("Test");
        	JavaResource javares = (JavaResource) rs.getResource(uri, nameContainsText);
        	if ((i % 10) == 0)
        	  LOGGER.info("Loading resource " + i + " of " + javafiles.size() + " for javafile " + path.getAbsolutePath() + "->"+ uri + "- Loaded: " + (javares != null && javares.isLoaded()) + "- Free memory: " + Runtime.getRuntime().freeMemory());
        	i++;
        	
        }

        return rs;
	}

	/**
	 * gets the file to create a backupfile to
	 * @param file originalfile
	 * @param sourcepath sourcepath
	 * @return backupfile
	 */
	private File getSaveFile (final File file, final File sourcepath) {
		File sourcesaved = new File (sourcepath.getParentFile(), "srcsaved");
		
		if (! file.getAbsolutePath().startsWith(sourcepath.getAbsolutePath()))
			throw new IllegalStateException("File " + file.getAbsolutePath() + " is not part of sourcepath " + sourcepath.getAbsolutePath());
		
		String relative = file.getAbsolutePath().substring(sourcepath.getAbsolutePath().length() + 1);
		return new File (sourcesaved, relative);
	}

	/**
	 * reads project-layout
	 * @param testSrc  testsrc-path
	 * @param saveHandling true: backups all javafiles
	 * @return resourceset
	 * @throws IOException
	 */
	public ResourceSet readProject (final File testSrc, final boolean saveHandling) throws IOException {
		JaMoPPUtil.initialize();
		
		LOGGER.fine ("Check Exludepaths " + configurator.getExcludePaths());

        List<File> javafiles = new ArrayList<File>();
        findJavafiles(javafiles, testSrc, testSrc, saveHandling);

        ResourceSet rs = createResourceSet(javafiles, testSrc);

        return rs;
	}


	/**
	 * migrates one class
	 * @param compilationunit  unit
	 * @param clazz clazz
	 * @throws JunitMigrationException exception
	 */
	public void migrateOneClass (final CompilationUnit compilationunit, final Class clazz) throws JunitMigrationException {
		for (JunitMigrator nextMigrator: configurator.getMigrators()) {
		    nextMigrator.migrateTestCase (compilationunit, clazz);
		  }
	}

	/**
	 * call cleanups for one class
	 * @param compilationunit unit
	 * @param clazz clazz
	 * @throws JunitMigrationException exception
	 */
	public void cleanupOneClass (final CompilationUnit compilationunit, final Class clazz) throws JunitMigrationException {
		for (JunitMigrator nextMigrator: configurator.getMigrators()) {
			nextMigrator.cleanupTestCase(compilationunit, clazz);
		  }
	}

	/**
	 * migrate a path to junit4
	 * @param testSrc testsrc-path
	 * @return result of migration
	 * @throws IOException Exception
	 */
	public JunitMigrationResult migratePath (final File testSrc) throws IOException {
		

		JunitMigrationResult result = new JunitMigrationResult();

		ResourceSet rs = readProject(testSrc, true);

		Collection <CompilationUnit> compilationunits = new ArrayList<CompilationUnit>();

		Resource[] resources = rs.getResources().toArray(new Resource[rs.getResources().size()]);

		int i = 0;
        for (Resource nextResource : resources) {
        	if ((i % 10) == 0)
        	  LOGGER.info("Migrating " + i + " of " + resources.length + " resources");
        	i++;

        	if (! nextResource.getURI().isFile())
        		continue;
        	JavaResource javares = (JavaResource) nextResource;

        	//migration-step
        	for (Object nextContent : javares.getContents()) {
        		if (nextContent instanceof CompilationUnit) {
        			CompilationUnit compilationunit = (CompilationUnit) nextContent;
        			try {
        				Class testCaseClazz = migrationutil.getTestCase(compilationunit);
        				File originalFile = new File (compilationunit.eResource().getURI().toFileString());
        				File saveFile = getSaveFile(originalFile, testSrc);
        				
        				if (! saveFile.getParentFile().exists())
        					saveFile.getParentFile().mkdirs();
        				
        				if (! saveFile.exists())
        				  FileUtils.copyFile(originalFile, saveFile);
        				
         				FileUtils.copyFile(saveFile, originalFile);
        				
        				if (testCaseClazz != null) {
        					migrateOneClass(compilationunit, testCaseClazz);
        					compilationunit.eResource().save(null);
        					compilationunits.add(compilationunit);
        			    }

        			} catch (JunitMigrationException e) {
        				LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        				result.addError(e.getLocalizedMessage(), nextResource, e);
        			} catch (Exception e) {
        				LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        				result.addError(e.getLocalizedMessage(), nextResource, e);
        			}
        		}
        		else
        			result.addError("Content " + nextContent + " is not a compilation unit", nextResource, null);
        	}
        }


      //cleanup-step
        for (CompilationUnit compilationunit : compilationunits) {
        	Resource nextResource = compilationunit.eResource();
        			try {
        				Class testCaseClazz = migrationutil.getTestCase(compilationunit);
        				if (testCaseClazz != null) {

        				  cleanupOneClass(compilationunit, testCaseClazz);

        				  nextResource.save(null);
        				  if (! nextResource.getErrors().isEmpty()) {
      		        		for (Diagnostic nextDiagnostic: nextResource.getErrors())
      		        			result.addError(nextDiagnostic.getLocation() + ":" + nextDiagnostic.getMessage(), nextResource, null);
      		        	    continue;
      		        	  }
        				}
        			} catch (JunitMigrationException e) {
        				LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        				result.addError(e.getLocalizedMessage(), nextResource, e);
        			} catch (Exception e) {
        				LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        				result.addError(e.getLocalizedMessage(), nextResource, e);
        			}
        }

        
        result.finish(); //for time measurements
        
        
        LOGGER.info("Duation : " + result.getDuration() + "ms");


        return result;
	}



	/**
	 * reverts the current path, that means it restores all created backupfiles
	 * and removes the save path recursively
	 * to originalfiles again
	 * @param testsrc testsrc-path
	 * @throws IOException exception
	 */
	public void revertPath(File testsrc) throws IOException {

		List<File> foundJavaFiles = new ArrayList<File>();
		findJavafiles(foundJavaFiles, testsrc, testsrc, true);
		for (File nextFile: foundJavaFiles) {
			File saveFile = getSaveFile(nextFile, testsrc);

			if (saveFile.exists()) {
				if (nextFile.exists())
					FileUtils.deleteQuietly(nextFile);

				LOGGER.fine("Moving " + saveFile.getAbsolutePath() + " to " + nextFile.getAbsolutePath());
				FileUtils.moveFile(saveFile, nextFile);
			}
		}
		
		//Remove all the empty subdirectories
		File sourcesaved = new File (testsrc.getParentFile(), "srcsaved");
		FileUtils.deleteDirectory(sourcesaved);
	}
}
